package de.suur.jarmi.client;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import de.suur.jarmi.common.Compressor;
import de.suur.jarmi.common.Serializer;

public class JarmiClientFactory {

	private class AsyncHandler implements InvocationHandler {

		private JarmiClient client;

		public AsyncHandler(JarmiClient client) {
			this.client = client;
		}

		@Override
		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			Serializable[] parameters = new Serializable[args.length - 1];
			for (int i = 0; i < args.length - 1; i++) {
				parameters[i] = (Serializable) args[i];
			}

			Class<?>[] parameterTypes = method.getParameterTypes();
			Class<?>[] newParameterTypes = new Class<?>[parameterTypes.length - 1];
			for (int i = 0; i < parameterTypes.length - 1; i++) {
				newParameterTypes[i] = parameterTypes[i];
			}

			AsyncCallback<?> callback = (AsyncCallback<?>) args[args.length - 1];
			client.requestAsync(method.getName(), parameters,
					newParameterTypes, callback);
			return null;
		}

	}

	private class Handler implements InvocationHandler {

		private JarmiClient client;

		public Handler(JarmiClient client) {
			this.client = client;
		}

		@Override
		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			Serializable[] parameters = null;
			if (args != null) {
				parameters = new Serializable[args.length];
				for (int i = 0; i < args.length; i++) {
					parameters[i] = (Serializable) args[i];
				}
			}

			Class<?>[] parameterTypes = method.getParameterTypes();

			Object response = client.request(method.getName(), parameters,
					parameterTypes);
			if (response == null) {
				return returnPrimitiveType(method.getReturnType());
			} else {
				return response;
			}
		}

		private Object returnPrimitiveType(Class<?> returnType) {
			if (returnType == byte.class) {
				return (byte) 0;
			} else if (returnType == int.class) {
				return (int) 0;
			} else if (returnType == short.class) {
				return (short) 0;
			} else if (returnType == long.class) {
				return 0L;
			} else if (returnType == float.class) {
				return 0.0f;
			} else if (returnType == double.class) {
				return 0.0d;
			} else if (returnType == char.class) {
				return '\u0000';
			} else if (returnType == boolean.class) {
				return false;
			} else {
				return null;
			}
		}

	}

	public <S> S createAsyncHttpClient(Class<S> asyncInterface,
			Class<?> serviceInterface, Compressor compressor,
			Serializer serializer, String host, int port, String path) {
		JarmiHttpClient client = new JarmiHttpClient(serviceInterface,
				compressor, serializer, host, port, path);
		InvocationHandler handler = new AsyncHandler(client);
		return createProxy(asyncInterface, handler);
	}

	public <S> S createAsyncSocketClient(Class<S> asyncInterface,
			Class<?> serviceInterface, Compressor compressor,
			Serializer serializer, String host, int port) {
		JarmiSocketClient client = new JarmiSocketClient(serviceInterface,
				compressor, serializer, host, port);
		InvocationHandler handler = new AsyncHandler(client);
		return createProxy(asyncInterface, handler);
	}

	public <S> S createHttpClient(Class<S> serviceInterface,
			Compressor compressor, Serializer serializer, String host,
			int port, String path) {
		JarmiHttpClient client = new JarmiHttpClient(serviceInterface,
				compressor, serializer, host, port, path);
		InvocationHandler handler = new Handler(client);
		return createProxy(serviceInterface, handler);
	}

	private <S> S createProxy(Class<S> service, InvocationHandler handler) {
		return (S) Proxy.newProxyInstance(service.getClassLoader(),
				new Class<?>[] {
					service
				}, handler);
	}

	public <S> S createSocketClient(Class<S> serviceInterface,
			Compressor compressor, Serializer serializer, String host, int port) {
		JarmiSocketClient client = new JarmiSocketClient(serviceInterface,
				compressor, serializer, host, port);
		InvocationHandler handler = new Handler(client);
		return createProxy(serviceInterface, handler);
	}

}
